/*
    SPDX-FileCopyrightText: 2022 KylinSoft Co., Ltd.

    SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "tablet_manager.h"
#include "composite.h"
#include "internal_client.h"
#include "main.h"
#include "screens.h"
#include "scene.h"
#include "unmanaged.h"
#include "wayland_server.h"
#include "workspace.h"
#include "x11client.h"
#include "abstract_wayland_output.h"
#include "effects.h"
#include "effectloader.h"
#include <kwineffects.h>

// Qt
#include <QDBusMessage>
#include <QDBusConnection>
#include <QStyleOption>
#include <QPainter>
#include <QX11Info>
#include <QFile>

namespace KWin
{

static const QString BLUR_EFFECT_NAME = "blur";
static const QString TRANSLUCENCY_EFFECT_NAME = "kwin4_effect_translucency";
static const QString SCALEDESKTOP_EFFECT_NAME = "kwin4_effect_scaledesktop";
static const QString KYLIN_STATUSMANAGER_SERVICE = "com.kylin.statusmanager.interface";
static const QString KYLIN_STATUSMANAGER_PATH = "/";
static const QString KYLIN_STATUSMANAGER_INTERFACE = "com.kylin.statusmanager.interface";

KWIN_SINGLETON_FACTORY(TabletManager)

TabletManager::TabletManager(QObject *parent)
    : QObject(parent)
    , m_background(new TabletBackground())
    , m_singleWinManager(nullptr)
    , m_bTabletMode(false)
    , m_existTabletDesktop(false)
    , m_effects(nullptr)
    , m_effectloader(nullptr)
{
    m_existTabletDesktop = QFile::exists(QStringLiteral("/usr/bin/ukui-tablet-desktop"));
    if (kwinApp()->operationMode() == Application::OperationMode::OperationModeX11) {
        m_background->setState(NET::SkipTaskbar | NET::SkipPager | NET::SkipSwitcher);
        // TODO: 在这块如果不设置窗口可见和背景模糊的话，X 下无法创建成功窗口并受窗管控制
        m_background->show();
        // KWindowEffects::enableBlurBehind(m_background->windowHandle(), true);
        setBlurBehind();
    }
    // Workspace is created after the wayland server is initialized.
    connect(kwinApp(), &Application::workspaceCreated, this, &TabletManager::init);
}

TabletManager::~TabletManager()
{
    // TODO: 此处调用析构会导致double free
    // if (m_background) {
    //     delete m_background;
    //     m_background = nullptr;
    // }
    if (m_singleWinManager) {
        delete m_singleWinManager;
        m_singleWinManager = nullptr;
    }
    m_effectloader = nullptr;
    m_effects = nullptr;
}

void TabletManager::init()
{
    if (m_background) {
        m_background->init();
    }

    // 延迟一个事件循环再初始化单窗口管理模块类对象，因为混成类是在Workspace的构造函数中调用的，而混成在启动函数中
    // 调用了 Workspace::self() 函数 ，所以混成类延迟了一个事件循环来保证Worksapce构造完成。因此此处也需要延迟
    // 一个事件循环来确保在特效启动过程中模糊特效插件已经加载完成。不然在 X11 环境下太早对背景窗口最小化会导致模糊失败。
    // 在这里使用混成的信号 compositingToggled(bool) 不是很合适，因为混成无法开启时，不会触发此信号。
    QTimer::singleShot(0, [this]() {
        if (!m_singleWinManager) {
            m_singleWinManager = new SingleWindowManager(this);
            m_singleWinManager->setBlurBackground(m_background);
        }
    });
    connect(Compositor::self(), &Compositor::compositingToggled, [this](bool active) {
        if (active) {
            m_effects = static_cast<EffectsHandlerImpl*>(effects);
            m_effectloader = m_effects->findChild<AbstractEffectLoader*>();
            connect(m_effectloader, &AbstractEffectLoader::effectLoaded, [this](KWin::Effect *effect, const QString &name){
                Q_UNUSED(effect)
                if (name == BLUR_EFFECT_NAME || name == TRANSLUCENCY_EFFECT_NAME) {
                    m_background->update();
                }
            });
        } else {
            m_effectloader = nullptr;
            m_effects = nullptr;
        }
    });

    QDBusMessage message = QDBusMessage::createMethodCall(KYLIN_STATUSMANAGER_SERVICE,
                                                          KYLIN_STATUSMANAGER_PATH,
                                                          KYLIN_STATUSMANAGER_INTERFACE,
                                                          QStringLiteral("get_current_tabletmode"));
    QDBusMessage response = QDBusConnection::sessionBus().call(message);
    if (response.type() == QDBusMessage::ReplyMessage) {
        bool bTabletMode = response.arguments().takeFirst().toBool();
        slotSwitchTabletMode(bTabletMode);
    } else {
        qDebug() << "TabletMode dbus is invalid !";
    }

    QDBusConnection::sessionBus().connect(KYLIN_STATUSMANAGER_SERVICE,
                                          KYLIN_STATUSMANAGER_PATH,
                                          KYLIN_STATUSMANAGER_INTERFACE,
                                          QStringLiteral("mode_change_signal"),
                                          this,
                                          SLOT(slotSwitchTabletMode(bool)));
}

void TabletManager::slotSwitchTabletMode(bool bSurfaceMode)
{
    if (m_bTabletMode == bSurfaceMode) {
        return;
    }
    m_bTabletMode = bSurfaceMode;
    Q_EMIT tabletModeChanged(m_bTabletMode);

    if (m_singleWinManager) {
        if (KWin::effects) {
            EffectsHandlerImpl *effect = static_cast<EffectsHandlerImpl *>(effects);
            bool isLoad = effect->isEffectLoaded(SCALEDESKTOP_EFFECT_NAME);
            qDebug() << __func__ << "kwin4_effect_scaledesktop is loaded: " << isLoad;
            if (isLoad && existTabletDesktop()) {
                // 桌面缩放动作需要 1000 ms
                QTimer::singleShot(1000, [this]() {
                    m_singleWinManager->switchTablet(m_bTabletMode);
                });
            } else {
                m_singleWinManager->switchTablet(m_bTabletMode);
            }
        } else {
            qDebug() << __func__ << "KWin::effects pointer is not initialized ! ! !";
            m_singleWinManager->switchTablet(m_bTabletMode);
        }
    }

    // 在平板模式启动 kwin 时，此时 effects 对象还未初始化，此时也没有必要进行窗口移动
    if (KWin::effects == nullptr) {
        return;
    }
    const auto stacking_order = workspace()->stackingOrder();
    for (auto it = stacking_order.rbegin(); it != stacking_order.rend(); ++it) {
        AbstractClient *c = qobject_cast<AbstractClient*>(*it);
        // 因为现在侧边栏没有设置skipXXX等属性，所以暂时使用标题栏名字判断
        if (!c || c->isDock() || c->isDesktop()) {
            continue;
        }
        if (m_bTabletMode) {
            m_windowDesktopMap.insert(c->internalId(), c->desktop());
            // Tablet 模式下所有工作区的窗口合并到一起
            moveWindowBetweenVDesktop(c->internalId(), c->desktop(), KWin::effects->currentDesktop());
        } else {
            if (m_windowDesktopMap.contains(c->internalId())) {
                // PC 模式下窗口还原到原来所在的工作区
                moveWindowBetweenVDesktop(c->internalId(), KWin::effects->currentDesktop(), m_windowDesktopMap.value(c->internalId()));
            }
        }
    }
    if (!m_bTabletMode) {
        m_windowDesktopMap.clear();
    }
}

void TabletManager::moveWindowBetweenVDesktop(QUuid windowId, int srcDesktopIndex, int destDesktopIndex)
{
    auto* window = KWin::effects->findWindow(windowId);
    if (window == nullptr || window->isOnAllDesktops()) {
        return;
    }

    auto desktopIndexList = window->desktops();
    if (!desktopIndexList.contains(srcDesktopIndex)) {
        return;
    }

    desktopIndexList.remove(desktopIndexList.indexOf(srcDesktopIndex));

    if (desktopIndexList.contains(destDesktopIndex)) {
        return;
    }

    desktopIndexList.append(destDesktopIndex);

    KWin::effects->windowToDesktops(window, desktopIndexList);
}

void TabletManager::toggleTabletMode()
{
    QDBusMessage call = QDBusMessage::createMethodCall(KYLIN_STATUSMANAGER_SERVICE,
                                                       KYLIN_STATUSMANAGER_PATH,
                                                       KYLIN_STATUSMANAGER_INTERFACE,
                                                       QStringLiteral("set_tabletmode"));
    call.setArguments({!m_bTabletMode, "kwin", "debug"});
    QDBusMessage response = QDBusConnection::sessionBus().call(call);
    if (response.type() != QDBusMessage::ReplyMessage) {
        qWarning() << "[=== checkmode ===]" << "set_tabletmode dbus not work, change locally";
        slotSwitchTabletMode(!m_bTabletMode);
    }
}

void TabletManager::setBlurBehind(double radius)
{
    if (kwinApp()->operationMode() == Application::OperationMode::OperationModeX11) {
        xcb_connection_t *c = QX11Info::connection();
        if (!c) {
            return;
        }
        const QByteArray effectName = QByteArrayLiteral("_KDE_NET_WM_BLUR_BEHIND_REGION");
        xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(c, false, effectName.length(), effectName.constData());
        QScopedPointer<xcb_intern_atom_reply_t, QScopedPointerPodDeleter> atom(xcb_intern_atom_reply(c, atomCookie, nullptr));
        if (!atom) {
            return;
        }

        QVector<uint32_t> data;
        QRegion region;
        data.reserve(region.rectCount() * 4 + 1); // 1 means radius
        for (const QRect &r : region) {
            // kwin on X uses device pixels, convert from logical
            auto dpr = qApp->devicePixelRatio();
            data << r.x() * dpr << r.y() * dpr << r.width() * dpr << r.height() * dpr;
        }
        data << radius;
        xcb_change_property(c, XCB_PROP_MODE_REPLACE, m_background->winId(), atom->atom, XCB_ATOM_CARDINAL, 32, data.size(), data.constData());
    } else {
        if (m_background && m_background->windowHandle()) {
            m_background->windowHandle()->setProperty("kwin_blur_strength", radius);
        }
    }
}

void TabletManager::showBackground(double radius)
{
    if (!m_singleWinManager) {
        return;
    }
    m_singleWinManager->showBackground();
    setBlurBehind(radius);
}

void TabletManager::hideBackground(double radius)
{
    if (!m_singleWinManager) {
        return;
    }
    m_singleWinManager->hideBackground();
    setBlurBehind(radius);
}

TabletBackground::TabletBackground()
    : QWidget()
{
    setWindowFlags(Qt::WindowStaysOnBottomHint | Qt::FramelessWindowHint);
    setAttribute(Qt::WA_ShowWithoutActivating);
    setAttribute(Qt::WA_TranslucentBackground);
    setFocusPolicy(Qt::NoFocus);
    setWindowTitle("tablet_blur_background");
}

TabletBackground::~TabletBackground()
{
    qDebug() << __func__;
}

void TabletBackground::init()
{
    slotGeometryChanged();
    connect(screens(), &Screens::geometryChanged, this, &TabletBackground::slotGeometryChanged);
}

void TabletBackground::setState(NET::States state)
{
    NETWinInfo info(QX11Info::connection(), winId(), QX11Info::appRootWindow(), NET::Properties(), NET::Properties2());
    info.setState(state, state);
}

void TabletBackground::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event);
    QStyleOption opt;
    opt.init(this);
    QPainter p(this);
    p.setPen(Qt::NoPen);
    // QColor color = qApp->palette().color(QPalette::Base);
    QColor color(64, 64, 64);
    // color.setAlpha(m_alpha);
    bool isShouldBlur = false;
    if (effects) {
        EffectsHandlerImpl *effect = static_cast<EffectsHandlerImpl *>(effects);
        bool isLoadBlurEffect = effect->isEffectLoaded(BLUR_EFFECT_NAME);
        qDebug() << __func__ << isLoadBlurEffect;
        if ( isLoadBlurEffect) {
            isShouldBlur = true;
        } else {
            isShouldBlur = false;
        }
    }
    if (isShouldBlur) {
        color.setAlpha(0);
    } else {
        color.setAlpha(255);
    }
    QPalette pal(this->palette());
    pal.setColor(QPalette::Window, QColor(color));
    this->setPalette(pal);
    QBrush brush = QBrush(color);
    p.setBrush(brush);
    p.drawRoundedRect(opt.rect, 0, 0);
    style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
}

void TabletBackground::slotGeometryChanged()
{
    qDebug() << __func__ << screens()->geometry();
    QRect screenGeo = screens()->geometry();
    setGeometry(screenGeo);
    setFixedSize(screenGeo.size());
}

SingleWindowManager::SingleWindowManager(QObject *parent)
    : QObject(parent)
    , m_blurHelperWindow(nullptr)
    , m_blurBackground(nullptr)
    , m_ignorePlaceCenterList()
    , m_bTabletMode(false)
{
    // 蓝牙、网络工具、搜索不需要进行居中处理
    m_ignorePlaceCenterList << "ukui-bluetooth" << "kylin-nm" << "ukui-search";
    connect(workspace(), &Workspace::clientAdded, this, &SingleWindowManager::handleClientAdded);
    connect(workspace(), &Workspace::clientRemoved, this, &SingleWindowManager::handleClientRemoved);
    connect(workspace(), &Workspace::clientActivated, this, &SingleWindowManager::handleClientActivated);
    connect(workspace(), &Workspace::clientMinimizedChanged, this, &SingleWindowManager::handleClientMinimizedChanged);
    //connect(workspace(), &Workspace::clientDemandsAttentionChanged, this, &SingleWindowManager::handleClientAdded);
    connect(screens(), &Screens::geometryChanged, this, &SingleWindowManager::handleScreenGeometryChanged);
}

SingleWindowManager::~SingleWindowManager() = default;

void SingleWindowManager::setBlurBackground(TabletBackground *blurBackground)
{
    if (!blurBackground) {
        return;
    }
    m_blurBackground = blurBackground;
    if (!m_blurHelperWindow && m_blurBackground) {
        const auto stacking_order = workspace()->stackingOrder();
        if (!stacking_order.isEmpty()) {
            for (int i = stacking_order.count() - 1; i > -1; --i) {
                AbstractClient *c = qobject_cast<AbstractClient*>(stacking_order.at(i));
                if (c && c->keepBelow() && c->caption().contains("tablet_blur_background")) {
                    // only on X11
                    m_blurHelperWindow = c;
                    // 设置毛玻璃背景窗口为全屏，防止在updateClientArea()->checkWorkspacePosition()时毛玻璃窗口被修改位置
                    m_blurHelperWindow->setFullScreen(true, false);
                    m_blurHelperWindow->setDisableActive();
                    m_blurHelperWindow->minimize(true);
                    connect(Compositor::self(), &Compositor::compositingToggled, [this](bool active){
                        if (active) {
                            if (m_blurHelperWindow->effectWindow()) {
                                m_blurHelperWindow->effectWindow()->setData(WindowForceBlurRole, QVariant(true));
                            }
                        }
                    });
                    break;
                }
            }
        } 
    }

    // kwin 启动时如果存在已经存在的窗口，需要进行判断是否需要将顶层最大化或者显示桌面
    switchTablet(TabletManager::self()->tabletMode());
}

void SingleWindowManager::switchTablet(bool bTabletMode)
{
    m_bTabletMode = bTabletMode;
    const auto stacking_order = workspace()->stackingOrder();
    if (stacking_order.isEmpty()) {
        return;
    }

    bool isTopWindowFound = false;
    bool isTopWindowMaximized = true;
    for (int i = stacking_order.count() - 1; i > -1; --i) {
        AbstractClient *c = qobject_cast<AbstractClient*>(stacking_order.at(i));
        if (!c) {
            continue;
        }
        if (TabletManager::self()->existTabletDesktop() && c->isDesktop()) {
            updateDesktopVisibleState(c);
        }
        // 侧边栏唤醒时不需要显示毛玻璃背景，侧边栏会自己绘制毛玻璃，所以使用标题栏名字将侧边栏移除
        if (c->isDock() || c->isDesktop() || c->caption().contains(QStringLiteral("sidebar")) || c->caption() == QStringLiteral("tablet-taskbar")) {
            continue;
        }
#ifndef QT_NO_DEBUG
        qDebug() << "SingleWindowManager::switchTablet, caption:" << c->caption() << m_bTabletMode << "WindowType: " << c->windowType() 
                 << "isModal: " << c->isModal() << "isDialog: " << c->isDialog() << "isSkipPager:" << c->skipPager()
                 << "isSkipSwitcher: " << c->skipSwitcher() << "isSkipTaskbar: " << c->skipSwitcher();
#endif
        if (m_bTabletMode) { // PC to Tablet
            if (!c->isMinimized()) {
                if (!isTopWindowFound) {
                    // 瞬态窗口
                    if (c->isTransient()) {
                        Placement::self()->placeCentered(c, workspace()->activeOutput()->geometry());
                        // 模式却换时更新一下还原状态的geometry是为了防止居中后，在updateClientArea()时又被移动到PC模式保存的位置（不再居中显示）
                        c->updateGeometryRestore();
                        isTopWindowMaximized = false;
                        if (c->mainClients().size() > 0) {
                            isTopWindowFound = false;
                            continue;
                        }
                        else {
                            // 系统应用-文件保护箱的鉴权窗口比较特殊，是文件保护箱自己实现的。鉴权通过后才会显示文件保护箱主界面窗口，显示
                            // 鉴权窗口时，鉴权窗口的属性为瞬态窗口，但是主界面窗口不存在。所以特种情况，需要将 isTopWindowFound 设为 true。
                            isTopWindowFound = true;
                            continue;
                        }
                    } else { // 主界面窗口
                        isTopWindowFound = true;
                        c->maximize(MaximizeFull);          //序号越大，越在顶层，只需要将既不是任务栏又不是桌面的最顶层次未最小化的窗口最大化

                        if (c->isResizable()) {
                            isTopWindowMaximized = true;
                        } else {
                            // 如果是固定大小的窗口，需要将窗口放到屏幕的正中间
                            Placement::self()->placeCentered(c, workspace()->activeOutput()->geometry());
                            c->updateGeometryRestore();
                            isTopWindowMaximized = false;
                        }
                    }
                } else {
                    qDebug() << "SingleWindowManager::switchTablet, minimize() caption:" << c->caption();
                    c->minimize(true);
                }
            }
        }
    }
    if (!isTopWindowMaximized && m_bTabletMode) {
        showBackground();
    } else {
        hideBackground();
    }
}

template <class T>
void SingleWindowManager::addTransient(QVector<T*> &clients, T *transientClient)
{
    clients.append(transientClient);
}

template <class T>
bool SingleWindowManager::removeTransient(QVector<T*> &clients, T *transientClient)
{
    const int remove = clients.indexOf(transientClient);
    if (remove == -1) {
        return false;
    }
    clients.removeAt(remove);
    return true;
}

void SingleWindowManager::updateDesktopVisibleState(AbstractClient *desktop)
{
    if (!desktop || !desktop->isDesktop()) {
        return;
    }
    if (desktop->caption().contains(QStringLiteral("ukui-tablet-desktop"))) {
        m_tabletDesktop = desktop;
        if (effects) {
            EffectsHandlerImpl *effect = static_cast<EffectsHandlerImpl *>(effects);
            bool isLoad = effect->isEffectLoaded(SCALEDESKTOP_EFFECT_NAME);
            // 如果桌面缩放特效加载了，则有特效去处理桌面的显示与隐藏
            if (isLoad)
                return;
        }
        qDebug() << __func__ << __LINE__ << "KWin::effects pointer is not initialized ! ! !" << m_bTabletMode;
        // 桌面缩放特效未加载，由 SingleWindowManager 管理桌面的显示与隐藏
        if (!m_bTabletMode) {
            m_tabletDesktop->hideClient();
        } else {
            m_tabletDesktop->showClient();
        }
    } else {
        m_pcDesktop = desktop;
        if (effects) {
            EffectsHandlerImpl *effect = static_cast<EffectsHandlerImpl *>(effects);
            bool isLoad = effect->isEffectLoaded(SCALEDESKTOP_EFFECT_NAME);
            // 如果桌面缩放特效加载了，则有特效去处理桌面的显示与隐藏
            if (isLoad)
                return;
        }
        qDebug() << __func__ << __LINE__ << "KWin::effects pointer is not initialized ! ! !" << m_bTabletMode;
        // 桌面缩放特效未加载，由 SingleWindowManager 管理桌面的显示与隐藏
        if (m_bTabletMode) {
            m_pcDesktop->hideClient();
        } else {
            m_pcDesktop->showClient();
        }
    }
}

void SingleWindowManager::showBackground()
{
    if(kwinApp()->operationMode() == Application::OperationMode::OperationModeX11) {
        if (m_blurHelperWindow) {
            m_blurHelperWindow->unminimize(true);
        }
        m_blurBackground->update();
    }  else {
        m_blurBackground->show();
        // 对于 Internal window, 直接设置属性即可，没有必要去调用 KWindowEffects 的接口
        m_blurBackground->windowHandle()->setProperty("kwin_blur", QRegion());
    }  
}

void SingleWindowManager::hideBackground()
{
#ifndef QT_NO_DEBUG
    qDebug() << __func__ << "Hide Background ! ! !";
#endif
    if(kwinApp()->operationMode() == Application::OperationMode::OperationModeX11) {
        if (m_blurHelperWindow) {
            m_blurHelperWindow->minimize(true);
        }
    } else {
        m_blurBackground->hide();
    }       
}

void SingleWindowManager::handleClientAdded(AbstractClient *client)
{
    if (!client) {
        return;
    }
    // 存在部分窗口，它的主界面窗口还没有 show 出来或者隐藏了，找不到主界面窗口指针，但是该窗口仍为瞬态窗口，这
    // 种窗口我们暂时不将其加到瞬态窗口列表里。
    if (client->isTransient() && client->mainClients().size() > 0) {
        addTransient(m_transientClients, client);
    }
    if (TabletManager::self()->existTabletDesktop() && client->isDesktop()) {
        if (client->caption().contains(QStringLiteral("ukui-tablet-desktop"))) {
            qDebug() << __func__ << __LINE__ << "ukui-tablet-desktop add" << m_bTabletMode;
            m_tabletDesktop = client;
            if (!m_bTabletMode) {
                m_tabletDesktop->hideClient();
            }
        } else {
            qDebug() << __func__ << __LINE__ << "peony-qt-desktop add" << m_bTabletMode;
            m_pcDesktop = client;
            if (m_bTabletMode) {
                m_pcDesktop->hideClient();
            }
        }
    }
    if (!m_bTabletMode) {
        return;
    }

#ifndef QT_NO_DEBUG
    qDebug() << __func__ << client->caption();
#endif
}

void SingleWindowManager::handleClientRemoved(AbstractClient *client)
{
    if (!client) {
        return;
    }
    bool isTransientRemoved = removeTransient(m_transientClients, client);
    if (!m_bTabletMode) {
        return;
    }
#ifndef QT_NO_DEBUG
    qDebug() << __func__ << client->caption();
#endif
    // 该窗口存在主界面窗口，移除此窗口后还有主界面窗口，直接return即可，不需要处理。但是 client->isTransient() 返回的值永远都
    // 是 false，因为 workspace 在发送 clientRemoved 信号前，已经将父子窗口全部清空关系了。所以要将瞬态窗口提前保存下来，利用自
    // 定义的 removeTransient() 函数来判断是否移除的是瞬态窗口。瞬态窗口不直接return的话可能会导致毛玻璃背景先隐藏再被显示出来。
    // wayland 环境下任务栏隐藏也会触发此槽函数，直接return即可。
    if (isTransientRemoved || client->isDock()
                           || client->caption().contains(QStringLiteral("sidebar"))
                           || client->caption().contains(QStringLiteral("ukui-screensaver-"))) {
        return;
    } else {
        // 窗口可以最大化，意味着毛玻璃背景窗口并没有显示出来
        if (client->isMaximizable()) {
            return;
        } else {
            // 窗口不能最大化，意味着毛玻璃背景显示出来了。所以关闭窗口时要隐藏毛玻璃背景窗口。
            qDebug() << __func__ << "Hide Background ! ! !";
            hideBackground();
        }
    }
}

void SingleWindowManager::handleClientActivated(AbstractClient *client)
{
    if (!m_bTabletMode || !client || client->caption() == QStringLiteral("tablet-taskbar")) {
        return;
    }

#ifndef QT_NO_DEBUG
    // transientFor() 拿到的返回值是不准确的， mainClients() 可以拿到所有的父窗口
    // 先不考虑 mainClients() 返回多个父窗口的情况，因为目前不确定是否会存在这种情况
    qDebug() << __func__ << "Caption: " << client->caption() << "WindowType: " << client->windowType()
             << "resourceName: " << client->resourceName() << "resourceClass: " << client->resourceClass()
             << "isDialog: " << client->isDialog() << "isUtility: " << client->isUtility()
             << "isMaximizable: " << client->isMaximizable() << "transientFor: " << client->transientFor()
             << "transients:" << client->transients() << "mainClients: " << client->mainClients();
#endif

    if (client->isDesktop() || client->isDock() || client == m_blurHelperWindow
                            /* || client->caption().contains(QStringLiteral("sidebar")) */) {
        hideBackground();
        return;
    }
    // wayland 中不存在不受窗管管理的窗口，所以也会拿到锁屏的激活信号
    // 2023年计划扩展窗口类型，暂时只能通过标题名判断
    if (client->caption().contains(QStringLiteral("sidebar")) || client->caption().contains(QStringLiteral("ukui-screensaver-"))) {
        return;
    }

    // 鉴权窗口，不需要对其他窗口进行任何处理
    QStringList authenticationAgents = {"polkit-ukui-authentication-agent-1"
                                      , "polkit-kde-authentication-agent-1"
                                      , "kylin-installer"};
    if (authenticationAgents.contains(client->resourceClass())) {
        qDebug() <<__func__ << client->resourceClass();
        Placement::self()->placeCentered(client, workspace()->activeOutput()->geometry());
        // 某些应用打开前必须鉴权，在显示鉴权窗口时，将毛玻璃背景显示出来。
        // 在应用内部操作时激活鉴权窗口时，显示出毛玻璃背景窗口也无可厚非。
        showBackground();
        return;
    }

    bool hasMaximizedWindow = true;

    if (client->isMaximizable()) {
        // 能最大化的窗口直接最大化
        client->maximize(MaximizeFull);
    } else {
        // utility 表示一个小的持久的实用程序窗口，例如调色板或工具箱，用户可能在工作时保持该窗口的打开状态，所以这种类型窗口也不应该被居中
        if (!m_ignorePlaceCenterList.contains(client->resourceClass()) && !client->isUtility()) {
            // 如果应用不能最大化，则居中显示
            Placement::self()->placeCentered(client, workspace()->activeOutput()->geometry());
        }
        // 继续寻找是否含有主界面窗口
        if (client->mainClients().size() > 0) {
            AbstractClient *mainInterfaceClient = client->mainClients().at(0);
            if (mainInterfaceClient->isMaximizable()) {
                // 设置主界面窗口最大化
                mainInterfaceClient->maximize(MaximizeFull);
            } else {
                if (!m_ignorePlaceCenterList.contains(client->resourceClass())) {
                    // 如果主界面窗口也不能最大化，主界面窗口也需要居中
                    Placement::self()->placeCentered(mainInterfaceClient, workspace()->activeOutput()->geometry());
                }
                hasMaximizedWindow = false;
            }
            // 如果主界面窗口之前被最小化过，则需要单独再取消最小化，否则只会显示子窗口
            if (mainInterfaceClient->isMinimized()) {
                mainInterfaceClient->unminimize(true);
            }
        } else {
            // 没有主界面窗口，则代表只有一个不能最大化的应用
            hasMaximizedWindow = false;
        }
    }

    makeOtherWindowMinimized(client->mainClients().size() > 0 ? client->mainClients().at(0) : client);
    if (hasMaximizedWindow) {
        qDebug() << __func__ << "Hide Background !!!";
        hideBackground();
    } else {
        qDebug() << __func__ << "Show Background !!!";
        showBackground();
    }
}

void SingleWindowManager::handleClientMinimizedChanged(AbstractClient *client)
{
    if (!m_bTabletMode || !client || !client->isMinimized()) {
        return;
    }

#ifndef QT_NO_DEBUG 
    qDebug() << __func__ << client->caption();
#endif
    // 该窗口存在主界面窗口，最小化此窗口后还有主界面窗口，直接return即可，不需要处理。
    // 存在窗口的主界面窗口还没有 show 出来或者隐藏了，该窗口为瞬态窗口，但是找不到主界面窗口
    if ((client->isTransient() && client->mainClients().size() > 0) || client->isSpecialWindow() || client == m_blurHelperWindow) {
        return;
    }
    const auto stacking_order = workspace()->stackingOrder();
    if (stacking_order.isEmpty()) {
        return;
    }

    for (int i = stacking_order.count() - 1; i > -1; --i) {
        AbstractClient *c = qobject_cast<AbstractClient*>(stacking_order.at(i));
        if (!c || c->isSpecialWindow() || c->skipTaskbar() || c->skipSwitcher() || c->skipPager() 
               || c->caption().contains(QStringLiteral("sidebar"))) {
            continue;
        }
        if (c == client && !c->isResizable()) {
            hideBackground();
        } else {
            break;
        }
    }
}

void SingleWindowManager::handleScreenGeometryChanged()
{
    if (!m_bTabletMode || !workspace()->activeClient()) {
        return;
    }
    AbstractClient *activeClient = workspace()->activeClient();
    if (activeClient->isDock() || activeClient->isDesktop()) {
        return;
    }
    qDebug() << __func__ << activeClient->caption();
    if (!activeClient->isMaximizable()) {
        Placement::self()->placeCentered(activeClient, workspace()->activeOutput()->geometry());
        activeClient->updateGeometryRestore();
    }
    if (activeClient->mainClients().size() > 0) {
        for (auto *c : activeClient->mainClients()) {
            if (!c->isMaximizable()) {
                Placement::self()->placeCentered(c, workspace()->activeOutput()->geometry());
                // 屏幕输出大小改变时更新一下还原状态的geometry是为了防止居中后，在updateClientArea()时又被移动到PC模式保存的位置（不再居中显示）
                c->updateGeometryRestore();
            }
        }
    }
}

void SingleWindowManager::makeOtherWindowMinimized(AbstractClient *activeClient)
{
#ifndef QT_NO_DEBUG
    qDebug() << __func__ << "Caption: " << activeClient->caption() << "WindowType: " << activeClient->windowType() 
             << "isModal: " << activeClient->isModal() << "isDialog: " << activeClient->isDialog();
#endif

    if (activeClient->isUtility() || activeClient->isSpecialWindow()
                                  || activeClient->caption().contains(QStringLiteral("sogouImeService"))
                                  || m_ignorePlaceCenterList.contains(activeClient->resourceClass())) {
        return;
    }

    bool has_transient_client = (activeClient->transients().size() > 0 ? true : false);
    for (Toplevel *client : workspace()->stackingOrder()) {
        AbstractClient *c = qobject_cast<AbstractClient*>(client);
        if (!c) {
            continue;
        }
        bool isIgnoreWindow = (c->isDock() || c->isDesktop() || c->caption() == QStringLiteral("tablet-taskbar"));
        if (isIgnoreWindow) {
            continue;
        }
        if (c == activeClient) {
            // 如果窗口有瞬态窗口，也一并拉回上层
            if (c->transients().size()) {
                for (auto it = c->transients().constBegin(); it != c->transients().constEnd(); ++it) {
                    (*it)->unminimize(true);
                    // utility 表示一个小的持久的实用程序窗口，例如调色板或工具箱，用户可能在工作时保持该窗口的打开状态，所以这种类型窗口也不应该被居中
                    if (!(*it)->isMaximizable() && !(*it)->isUtility()) {
                        Placement::self()->placeCentered(*it, workspace()->activeOutput()->geometry());
                    }
                }
            }
            if (!has_transient_client) {
                break;
            }
        }
        // 如果激活的窗口存在瞬态窗口，需要将主界面窗口和瞬态窗口之间的其他应用的窗口最小化（存在这种可能性）。
        else if (has_transient_client && activeClient->transients().contains(c)) {
            break;
        }
        else if (!c->isMinimized())
        {
            qDebug() << __func__ << "exec minimize" << c->caption();
            c->minimize(true);
        }
    }
}

}
